package com.xuyanze.es;

import com.xuyanze.es.face.*;
import com.xuyanze.es.model.EsSegment;
import com.xuyanze.es.utils.EsWrapperProcessor;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.search.aggregations.AbstractAggregationBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;

import static com.xuyanze.es.enums.EsJoinType.*;
import static com.xuyanze.es.enums.EsKeyWord.*;

/**
 * @author ：lzy
 * @date ：2023-01-12
 * @description：
 */
public abstract class EsAbstractWrapper<T,R,Children extends EsAbstractWrapper<T,R,Children>>
    implements Compare<Children,R>, Nested<Children,Children>, Func<Children,R>, Match<Children,R> {

    private Children typeThis = (Children)this;

    private List<EsSegment> segments = new ArrayList<>();
    private List<SortBuilder> sortBuilderList = new ArrayList<>();
    private String[] withFields;
    private Pageable pageable;
    private Integer minimumShouldMatch;
    private boolean constScore;
    private AbstractAggregationBuilder aggregationBuilder;


    public List<EsSegment> getSegments() {
        return segments;
    }

    public List<SortBuilder> getSortBuilderList() {
        return sortBuilderList;
    }

    public AbstractAggregationBuilder getAggregationBuilder() {
        return aggregationBuilder;
    }

    public void setAggregationBuilder(AbstractAggregationBuilder aggregationBuilder) {
        this.aggregationBuilder = aggregationBuilder;
    }

    /**
     * 只包含指定字段
     * @param withFields
     * @return
     */
    public Children setWithFields(String ...withFields){
        this.withFields = withFields;
        return typeThis;
    }

    public String[] getWithFields() {
        return withFields;
    }

    public void setMinimumShouldMatch(Integer minimumShouldMatch) {
        this.minimumShouldMatch = minimumShouldMatch;
    }

    public Integer getMinimumShouldMatch() {
        return minimumShouldMatch;
    }

    public Pageable getPageable() {
        return pageable;
    }



    //################################# Compare #################################

    @Override
    public Children eq(R field, Object val) {
        segments.add(EsSegment.of(MUST, EQ, fieldToString(field), val));
        return typeThis;
    }

    @Override
    public Children eqTerms(R field, Object[] vals) {
        segments.add(EsSegment.of(MUST, EQ_TERMS, fieldToString(field), vals));
        return typeThis;
    }

    @Override
    public Children eqTerms(String field, Object[] vals) {
        segments.add(EsSegment.of(MUST, EQ_TERMS, field, vals));
        return typeThis;
    }

    @Override
    public Children eq(String field, Object val) {
        segments.add(EsSegment.of(MUST, EQ, field, val));
        return typeThis;
    }

    @Override
    public Children eqWithFilter(R field, Object val) {
        segments.add(EsSegment.of(FILTER, EQ, fieldToString(field), val));
        return typeThis;
    }

    @Override
    public Children eqWithFilter(String field, Object val) {
        segments.add(EsSegment.of(FILTER, EQ, field, val));
        return typeThis;
    }

    @Override
    public Children eqKeyWord(R field, Object val) {
        segments.add(EsSegment.of(MUST, EQ, fillKeyWordField(field),val));
        return typeThis;
    }

    @Override
    public Children gt(R field, Object val) {
        segments.add(EsSegment.of(MUST, GT, fieldToString(field), val));
        return typeThis;
    }

    @Override
    public Children gtWithFilter(R field, Object val) {
        segments.add(EsSegment.of(FILTER, GT, fieldToString(field), val));
        return typeThis;
    }

    @Override
    public Children ge(R field, Object val) {
        segments.add(EsSegment.of(MUST, GE, fieldToString(field), val));
        return typeThis;
    }

    @Override
    public Children geWithFilter(R field, Object val) {
        segments.add(EsSegment.of(FILTER, GE, fieldToString(field), val));
        return typeThis;
    }

    @Override
    public Children lt(R field, Object val) {
        segments.add(EsSegment.of(MUST, LT, fieldToString(field), val));
        return typeThis;
    }

    @Override
    public Children ltWithFilter(R field, Object val) {
        segments.add(EsSegment.of(FILTER, LT, fieldToString(field), val));
        return typeThis;
    }

    @Override
    public Children le(R field, Object val) {
        segments.add(EsSegment.of(MUST, LE, fieldToString(field), val));
        return typeThis;
    }

    @Override
    public Children leWithFilter(R field, Object val) {
        segments.add(EsSegment.of(FILTER, LE, fieldToString(field), val));
        return typeThis;
    }

    @Override
    public Children ne(R field, Object val) {
        segments.add(EsSegment.of(MUST_NOT, EQ, fieldToString(field), val));
        return typeThis;
    }

    @Override
    public Children like(R field, Object val) {
        segments.add(EsSegment.of(MUST, LIKE, fieldToString(field), val));
        return typeThis;
    }

    @Override
    public Children likeKeyWord(R field, Object val) {
        segments.add(EsSegment.of(MUST, LIKE, fillKeyWordField(field), val));
        return typeThis;
    }

    @Override
    public Children notLike(R field, Object val) {
        segments.add(EsSegment.of(MUST_NOT, LIKE, fieldToString(field), val));
        return typeThis;
    }

    @Override
    public Children notLikeKeyWord(R field, Object val) {
        segments.add(EsSegment.of(MUST_NOT, LIKE, fillKeyWordField(field), val));
        return typeThis;
    }

    @Override
    public Children leftLike(R field, Object val) {
        segments.add(EsSegment.of(MUST, LIKE_LEFT, fieldToString(field), val));
        return typeThis;
    }

    @Override
    public Children leftLikeKeyWord(R field, Object val) {
        segments.add(EsSegment.of(MUST, LIKE_LEFT, fillKeyWordField(field), val));
        return typeThis;
    }

    @Override
    public Children rightLike(R field, Object val) {
        segments.add(EsSegment.of(MUST, LIKE_RIGHT, fieldToString(field), val));
        return typeThis;
    }

    @Override
    public Children rightLikeKeyWord(R field, Object val) {
        segments.add(EsSegment.of(MUST, LIKE_RIGHT, fillKeyWordField(field), val));
        return typeThis;
    }

    @Override
    public Children between(R field, Object from,Object to) {
        segments.add(EsSegment.of(MUST, BETWEEN, fieldToString(field), from, to));
        return typeThis;
    }

    @Override
    public Children notBetween(R field, Object from,Object to) {
        segments.add(EsSegment.of(MUST_NOT, BETWEEN, fieldToString(field), from, to));
        return typeThis;
    }

    //################################# Nested #################################
    @Override
    public Children and(Consumer<Children> consumer) {
        EsLambdaQueryWrapper<T> wrapper = instance();
        consumer.accept((Children) wrapper);
        BoolQueryBuilder childQueryBuilder = EsWrapperProcessor.initQueryBuilder(wrapper);
        Optional.ofNullable(childQueryBuilder).ifPresent(
                k->segments.add(EsSegment.of(MUST, CHILDREN_QUERY,childQueryBuilder)
        ));
        return typeThis;
    }

    @Override
    public Children or(Consumer<Children> consumer) {
        EsLambdaQueryWrapper<T> wrapper = instance();
        consumer.accept((Children) wrapper);
        BoolQueryBuilder childQueryBuilder = EsWrapperProcessor.initQueryBuilder(wrapper);
        Optional.ofNullable(childQueryBuilder).ifPresent(
                k->segments.add(EsSegment.of(SHOULD, CHILDREN_QUERY,childQueryBuilder))
        );
        return typeThis;
    }

    @Override
    public Children or() {
        segments.add(EsSegment.of(SHOULD, OR_JOIN));
        return typeThis;
    }

    //################################# Func #################################

    @Override
    public Children orderByAsc(R field) {
        sortBuilderList.add(SortBuilders.fieldSort(fieldToString(field)).order(SortOrder.ASC));
        return typeThis;
    }

    @Override
    public Children orderByDesc(R field) {
        sortBuilderList.add(SortBuilders.fieldSort(fieldToString(field)).order(SortOrder.DESC));
        return typeThis;
    }

    public Children setPageable(int pageNum, int pageSize) {
        if(pageNum-1 < 0){
            throw new RuntimeException("pageNum must ge 1!");
        }
        pageable = PageRequest.of(pageNum-1,pageSize);
        return typeThis;
    }

    @Override
    public Children isNull(R field) {
        segments.add(EsSegment.of(MUST_NOT, EXISTS, fieldToString(field)));
        return typeThis;
    }

    @Override
    public Children isNotNull(R field) {
        segments.add(EsSegment.of(MUST, EXISTS, fieldToString(field)));
        return typeThis;
    }


    //################################# Match #################################

    /**
     * 最多返回1w条
     * @return
     */
    @Override
    public Children matchAllQuery() {
        segments.add(EsSegment.of(MUST, MATCH_ALL_QUERY));
        return typeThis;
    }

    @Override
    public Children matchPhraseQuery(R field, Object val) {
        segments.add(EsSegment.of(MUST, MATCH_PHRASE_QUERY, fieldToString(field), val));
        return typeThis;
    }

    @Override
    public Children matchPhraseQuery(R field, Object val, float boost) {
        segments.add(EsSegment.of(MUST, MATCH_PHRASE_QUERY, fieldToString(field), val).setBoost(boost));
        return typeThis;
    }

    @Override
    public Children matchQuery(R field, Object val, float boost) {
        segments.add(EsSegment.of(MUST, MATCH_QUERY, fieldToString(field), val).setBoost(boost));
        return typeThis;
    }

    @Override
    public Children matchQuery(R field, Object val) {
        segments.add(EsSegment.of(MUST, MATCH_QUERY, fieldToString(field), val));
        return typeThis;
    }

    public abstract EsLambdaQueryWrapper<T> instance();

    public String fieldToString(R field) {
        return (String)field;
    }

    private String fillKeyWordField(R field) {
        return String.format("%s.keyword", fieldToString(field));
    }

    /**
     * Elasticsearch 会基于使用频次自动缓存查询。
     * 如果一个非评分查询在最近的 256 次查询中被使用过（次数取决于查询类型），那么这个查询就会作为缓存的候选。
     * 但是，并不是所有的片段都能保证缓存 bitset 。只有那些文档数量超过 10,000 （或超过总文档数量的 3% )才会缓存 bitset 。
     * 因为小的片段可以很快的进行搜索和合并，这里缓存的意义不大。
     * @param constScore
     * @return
     */
    public Children setConstScore(boolean constScore) {
        this.constScore = constScore;
        return typeThis;
    }

    public boolean isConstScore() {
        return constScore;
    }
}
